
package com.jf.cloud.product.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.PhoneUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.builder.ExcelWriterBuilder;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.jf.cloud.api.product.bo.SpuSimpleBO;
import com.jf.cloud.common.constant.Constant;
import com.jf.cloud.common.database.dto.PageDTO;
import com.jf.cloud.common.database.util.PageUtil;
import com.jf.cloud.common.database.vo.PageVO;
import com.jf.cloud.common.exception.LuckException;
import com.jf.cloud.common.i18n.I18nMessage;
import com.jf.cloud.common.product.constant.StockBillType;
import com.jf.cloud.common.product.constant.StockType;
import com.jf.cloud.common.product.dto.ProductSearchDTO;
import com.jf.cloud.common.product.vo.SkuVO;
import com.jf.cloud.common.product.vo.StockBillLogItemVO;
import com.jf.cloud.common.security.AuthUserContext;
import com.jf.cloud.common.util.ExcelUtil;
import com.jf.cloud.product.constant.ProdKeyType;
import com.jf.cloud.product.dto.StockBillLogDTO;
import com.jf.cloud.product.dto.StockBillLogItemDTO;
import com.jf.cloud.product.mapper.SkuMapper;
import com.jf.cloud.product.model.SkuStock;
import com.jf.cloud.product.service.SkuService;
import com.jf.cloud.product.service.SkuStockService;
import com.jf.cloud.product.service.SpuService;
import com.jf.cloud.product.service.StockBillLogItemService;
import com.jf.cloud.product.vo.SkuExcelVO;
import com.jf.cloud.product.vo.StockBillLogItemExcelVO;
import com.jf.cloud.product.vo.StockBillLogItemImportExcelVO;
import com.jf.cloud.product.vo.StockBillLogItemImportVO;
import com.jf.cloud.product.vo.mongo.MongoStockBillLogItemVO;
import ma.glasnost.orika.MapperFacade;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.*;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletResponse;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * 出入库商品项
 *
 * @author zz
 * @date 2022-06-22 16:57:27
 */
@Service
public class StockBillLogItemServiceImpl implements StockBillLogItemService {

    private final Logger logger = LoggerFactory.getLogger(StockBillLogItemServiceImpl.class);

    @Autowired
    private SkuService skuService;
    @Autowired
    private MongoTemplate mongoTemplate;
    @Autowired
    private SkuStockService skuStockService;
    @Autowired
    private MapperFacade mapperFacade;
    @Autowired
    private SpuService spuService;
    @Autowired
    private SkuMapper skuMapper;

    @Value("${mall4cloud.expose.operation.auth:}")
    private Boolean permission;

    private static final Logger log = LoggerFactory.getLogger(StockBillLogItemServiceImpl.class);

    @Override
    public PageVO<MongoStockBillLogItemVO> page(PageDTO pageDTO, StockBillLogDTO stockBillLogDTO) {
        // 主表查询条件
        Criteria itemCriteria = getItemCriteriaByParam(stockBillLogDTO);
        // 主表返回字段
        ProjectionOperation itemProjectionOperation = Aggregation.project("stockCount","unitPrice","spuId","skuId","stockBillLogId")
                .and("_id").as("stockBillLogItemId");
        // 关联stockBillLog表
        LookupOperation logLookupOperation = Aggregation.lookup("stock_bill_log", "stockBillLogId", "_id", "stockBillLogs");
        // 展开联表字段
        UnwindOperation unwindOperation = Aggregation.unwind("stockBillLogs");
        // 获取关联到stockBillLog的查询聚合条件
        Criteria logCriteria = getLogCriteriaByParam(stockBillLogDTO);
        // 联表返回字段
        ProjectionOperation logProjectOperation = Aggregation.project("stockCount","unitPrice","spuId","skuId","stockBillLogId")
                .and("_id").as("stockBillLogItemId")
                .and("stockBillLogs.stockBillType").as("stockBillType")
                .and("stockBillLogs.makerMobile").as("makerMobile")
                .and("stockBillLogs.stockBillNo").as("stockBillNo")
                .and("stockBillLogs.sourceOrderNo").as("sourceOrderNo")
                .and("stockBillLogs.type").as("stockType")
                .and("stockBillLogs.createTime").as("createTime");
        SortOperation sortOperation = Aggregation.sort(Sort.Direction.DESC, "createTime").and(Sort.Direction.DESC, "stockBillLogItemId");
        // 分页数据
        SkipOperation skipOperation = Aggregation.skip((long) pageDTO.getPageSize() * (pageDTO.getPageNum() - 1));
        LimitOperation limitOperation = Aggregation.limit(pageDTO.getPageSize());
        TypedAggregation<MongoStockBillLogItemVO> typedAggregation = Aggregation.newAggregation(
                MongoStockBillLogItemVO.class,
                Aggregation.match(itemCriteria),
                itemProjectionOperation,
                logLookupOperation,
                unwindOperation,
                Aggregation.match(logCriteria),
                logProjectOperation,
                sortOperation,
                skipOperation,
                limitOperation
        );
        logger.info("pageStockItem()的mongodb查询语句： {}", typedAggregation);
        // 执行聚合操作获取当前页数据
        AggregationResults<MongoStockBillLogItemVO> aggregationResults = mongoTemplate.aggregate(typedAggregation, MongoStockBillLogItemVO.class);
        List<MongoStockBillLogItemVO> curList = aggregationResults.getMappedResults();
        List<Long> skuIds = curList.stream().map(MongoStockBillLogItemVO::getSkuId).collect(Collectors.toList());
        ProductSearchDTO productSearchDTO = new ProductSearchDTO();
        productSearchDTO.setSkuIds(skuIds);
        List<SkuExcelVO> skuList = skuService.exportSkuList(productSearchDTO);
        Map<Long, SkuExcelVO> skuMap = skuList.stream().collect(Collectors.toMap(SkuExcelVO::getSkuId, s -> s));
        for (MongoStockBillLogItemVO mongoStockBillLogItem : curList) {
            if (!skuMap.containsKey(mongoStockBillLogItem.getSkuId())) {
                continue;
            }
            SkuExcelVO skuExcelVO = skuMap.get(mongoStockBillLogItem.getSkuId());
            mongoStockBillLogItem.setPartyCode(skuExcelVO.getPartyCode());
            mongoStockBillLogItem.setSpuName(skuExcelVO.getSpuName());
            if(BooleanUtil.isFalse(permission) && StrUtil.isNotBlank(mongoStockBillLogItem.getMakerMobile())){
                mongoStockBillLogItem.setMakerMobile(PhoneUtil.hideBetween(mongoStockBillLogItem.getMakerMobile()).toString());
            }
        }
        // 总条数
        TypedAggregation<MongoStockBillLogItemVO> totalTypedAggregation =
                Aggregation.newAggregation(
                        MongoStockBillLogItemVO.class,
                        Aggregation.match(itemCriteria),
                        itemProjectionOperation,
                        logLookupOperation,
                        unwindOperation,
                        Aggregation.match(logCriteria),
                        logProjectOperation
                );
        AggregationResults<MongoStockBillLogItemVO> aggregate = mongoTemplate.aggregate(totalTypedAggregation, MongoStockBillLogItemVO.class);
        List<MongoStockBillLogItemVO> mappedResults = aggregate.getMappedResults();
        PageVO<MongoStockBillLogItemVO> stockBillLogItemPage = new PageVO<>();
        stockBillLogItemPage.setTotal((long)mappedResults.size());
        stockBillLogItemPage.setList(curList);
        stockBillLogItemPage.setPages(PageUtil.getPages(mappedResults.size(), pageDTO.getPageSize()));
        return stockBillLogItemPage;
    }

    @Override
    public List<StockBillLogItemExcelVO> exportFlow(StockBillLogDTO stockBillLogDTO, HttpServletResponse response) {
        // 主表查询条件
        Criteria itemCriteria = getItemCriteriaByParam(stockBillLogDTO);
        // 主表返回字段
        ProjectionOperation itemProjectionOperation = Aggregation.project("stockCount","unitPrice","afterStock","skuId","stockBillLogId")
                .and("_id").as("stockBillLogItemId");
        // 关联stockBillLog表
        LookupOperation logLookupOperation = Aggregation.lookup("stock_bill_log", "stockBillLogId", "_id", "stockBillLogs");
        // 展开联表字段
        UnwindOperation unwindOperation = Aggregation.unwind("stockBillLogs");
        // 获取关联到stockBillLog的查询聚合条件
        Criteria logCriteria = getLogCriteriaByParam(stockBillLogDTO);
        // 联表返回字段
        ProjectionOperation logProjectOperation = Aggregation.project("stockCount","unitPrice","afterStock","skuId","stockBillLogId")
                .and("_id").as("stockBillLogItemId")
                .and("stockBillLogs.stockBillType").as("stockBillType")
                .and("stockBillLogs.stockBillNo").as("stockBillNo")
                .and("stockBillLogs.sourceOrderNo").as("sourceOrderNo")
                .and("stockBillLogs.createTime").as("createTime");
        SortOperation sortOperation = Aggregation.sort(Sort.Direction.DESC, "createTime").and(Sort.Direction.DESC, "stockBillLogItemId");
        TypedAggregation<MongoStockBillLogItemVO> typedAggregation = Aggregation.newAggregation(
                MongoStockBillLogItemVO.class,
                Aggregation.match(itemCriteria),
                itemProjectionOperation,
                logLookupOperation,
                unwindOperation,
                Aggregation.match(logCriteria),
                logProjectOperation,
                sortOperation
        );
        logger.info("exportFlow()的mongodb查询语句： {}", typedAggregation);
        // 执行聚合操作获取当前页数据
        AggregationResults<MongoStockBillLogItemVO> aggregationResults = mongoTemplate.aggregate(typedAggregation, MongoStockBillLogItemVO.class);
        List<MongoStockBillLogItemVO> mappedResults = aggregationResults.getMappedResults();
        List<StockBillLogItemExcelVO> excelStockBillLogItemList = mapperFacade.mapAsList(mappedResults, StockBillLogItemExcelVO.class);
        List<Long> skuIds = excelStockBillLogItemList.stream().map(StockBillLogItemExcelVO::getSkuId).collect(Collectors.toList());
        ProductSearchDTO productSearchDTO = new ProductSearchDTO();
        productSearchDTO.setSkuIds(skuIds);
        List<SkuExcelVO> skuList = skuService.exportSkuList(productSearchDTO);
        Map<Long, SkuExcelVO> skuMap = skuList.stream().collect(Collectors.toMap(SkuExcelVO::getSkuId, s -> s));
        for (StockBillLogItemExcelVO stockBillLogItemExcel: excelStockBillLogItemList) {
            stockBillLogItemExcel.setStockBillTypeName(Objects.nonNull(stockBillLogItemExcel.getStockBillType()) ? StockBillType.instance(stockBillLogItemExcel.getStockBillType()).text() : "");
            if (!skuMap.containsKey(stockBillLogItemExcel.getSkuId())) {
                continue;
            }
            SkuExcelVO skuExcelVO = skuMap.get(stockBillLogItemExcel.getSkuId());
            stockBillLogItemExcel.setSpuName(skuExcelVO.getSpuName());
            stockBillLogItemExcel.setSkuName(skuExcelVO.getSkuName());
            stockBillLogItemExcel.setPartyCode(skuExcelVO.getPartyCode());
        }
        return excelStockBillLogItemList;
    }

    @Override
    public void downloadModel(HttpServletResponse response, Integer type) {
        List<StockBillLogItemExcelVO> list = new ArrayList<>();
        Map<Integer, String[]> map = new HashMap<>(8);
        list.add(new StockBillLogItemExcelVO());
        ExcelWriter excelWriter = null;
        try{
            // 先执行合并策略
            ExcelWriterBuilder excelWriterMerge = ExcelUtil.getExcelWriterMerge(response, Objects.equals(type, StockType.OUT_OF_STOCK.value()) ? StockBillLogItemImportExcelVO.EXCEL_NAME_EXPORT : StockBillLogItemImportExcelVO.EXCEL_NAME_IMPORT, StockBillLogItemImportExcelVO.MERGE_ROW_INDEX, StockBillLogItemImportExcelVO.MERGE_COLUMN_INDEX);
            excelWriter = ExcelUtil.getExcelModel(excelWriterMerge, map, Constant.START_ROW).build();
            // 业务代码
            if (CollUtil.isNotEmpty(list)) {
                // 进行写入操作
                WriteSheet sheetWriter = EasyExcel.writerSheet(Objects.equals(type, StockType.OUT_OF_STOCK.value()) ? StockBillLogItemImportExcelVO.SHEET_NAME_EXPORT : StockBillLogItemImportExcelVO.SHEET_NAME_IMPORT).head(StockBillLogItemImportExcelVO.class).build();
                excelWriter.write(list,sheetWriter);
            }

        } catch (Exception e) {
            log.error("导出出入库商品项模板excel失败", e);
        }finally {
            // 千万别忘记finish 会帮忙关闭流
            if (excelWriter != null) {
                excelWriter.finish();
            }
        }

    }

    @Override
    public void importExcel(List<StockBillLogItemImportExcelVO> list, List<String> errorList, Integer stockType, StockBillLogItemImportVO stockBillLogItemImportVO) {
        Long shopId = AuthUserContext.get().getTenantId();
        List<StockBillLogItemVO> stockBillLogItemVOList = new ArrayList<>();
        int i= 0;
        String tips = "";
        if(list.size() == 0){
            tips = "导入数据为空，请检查下文件";
            stockBillLogItemImportVO.setTips(tips);
        }else{
            try {
                for(StockBillLogItemImportExcelVO item : list){
                    i++;
                    String partyCode = item.getPartyCode();
                    if(Objects.isNull(partyCode)){
                        throw new LuckException("商品编码不能为空");
                    }

                    StockBillLogItemDTO stockBillLogItemDTO = skuService.getSkuByPartyCodeAndShopId(partyCode, shopId);
                    if (Objects.isNull(stockBillLogItemDTO)) {
                        throw new LuckException("商品不存在");
                    }
                    if(Objects.isNull(item.getStockCount())){
                        throw new LuckException("出入库数量不能为空");
                    }
                    if(Objects.equals(stockType, StockType.WAREHOUSING.value()) && item.getStockCount() <= 0){
                        throw new LuckException("入库数量不能少于零");
                    }
                    SkuStock skuStock = skuStockService.getBySkuId(stockBillLogItemDTO.getSkuId());
                    if(Objects.equals(stockType, StockType.OUT_OF_STOCK.value())){
                        if(Objects.nonNull(skuStock) && item.getStockCount() > skuStock.getStock()){
                            throw new LuckException("出库数量不能超过商品库存");
                        }
                    }
                    StockBillLogItemVO stockBillLogItemVO = new StockBillLogItemVO();
                    stockBillLogItemVO.setSkuId(stockBillLogItemDTO.getSkuId());
                    stockBillLogItemVO.setPartyCode(item.getPartyCode());
                    stockBillLogItemVO.setStock(skuStock.getStock());
                    stockBillLogItemVO.setMainImgUrl(stockBillLogItemDTO.getMainImgUrl());
                    stockBillLogItemVO.setStockBillType(stockType);
                    stockBillLogItemVO.setSpuId(stockBillLogItemDTO.getSpuId());
                    stockBillLogItemVO.setStockCount(item.getStockCount());
                    stockBillLogItemVOList.add(stockBillLogItemVO);
                }
            } catch (LuckException e) {
                //错误信息提示
                errorList.add("第" + (i + 1) + "行数据有错误：" + e.getMessage());
            }
            tips = "检查到" + list.size() + "条商品信息，成功导入" +
                    (stockBillLogItemVOList.size()) + "条商品信息！错误" + (list.size() - stockBillLogItemVOList.size()) + "条商品信息!" +
                    (CollUtil.isNotEmpty(errorList) ? errorList.toString() : "");
        }

        stockBillLogItemImportVO.setTips(tips);
        stockBillLogItemImportVO.setStockBillLogItemList(stockBillLogItemVOList);
    }

    private Criteria getLogCriteriaByParam(StockBillLogDTO stockBillLogDTO) {
        Criteria criteria = new Criteria();
        criteria.and("stockBillLogs.shopId").is(stockBillLogDTO.getShopId());
        if (Objects.nonNull(stockBillLogDTO.getCreateStartTime()) && Objects.nonNull(stockBillLogDTO.getCreateEndTime())) {
            criteria.andOperator(Criteria.where("stockBillLogs.createTime").lte(stockBillLogDTO.getCreateEndTime()),
                    Criteria.where("stockBillLogs.createTime").gte(stockBillLogDTO.getCreateStartTime()));
        }
        if (StrUtil.isNotBlank(stockBillLogDTO.getSourceOrderNo())) {
            // 模糊查询只支持字段属性是字符串
            criteria.and("stockBillLogs.sourceOrderNo").regex(Pattern.compile("^.*" + stockBillLogDTO.getSourceOrderNo() + ".*$", Pattern.CASE_INSENSITIVE));
        }
        if (StrUtil.isNotBlank(stockBillLogDTO.getStockBillNo())) {
            criteria.and("stockBillLogs.stockBillNo").regex(Pattern.compile("^.*" + stockBillLogDTO.getStockBillNo() + ".*$", Pattern.CASE_INSENSITIVE));
        }
        if (Objects.nonNull(stockBillLogDTO.getStockBillType()) && !Objects.equals(stockBillLogDTO.getStockBillType(), 0)) {
            criteria.and("stockBillLogs.stockBillType").is(stockBillLogDTO.getStockBillType());
        }
        if (Objects.nonNull(stockBillLogDTO.getType())) {
            criteria.and("stockBillLogs.type").is(stockBillLogDTO.getType());
        }
        if (Objects.nonNull(stockBillLogDTO.getStatus())) {
            criteria.and("stockBillLogs.status").is(stockBillLogDTO.getStatus());
        }
        if (StrUtil.isNotBlank(stockBillLogDTO.getMakerMobile())) {
            criteria.and("stockBillLogs.makerMobile").is(stockBillLogDTO.getMakerMobile());
        }
        return criteria;
    }

    private Criteria getItemCriteriaByParam(StockBillLogDTO stockBillLogDTO) {
        Criteria criteria = new Criteria();
        if (StrUtil.isNotBlank(stockBillLogDTO.getProdKey())) {
            if (Objects.equals(stockBillLogDTO.getProdKeyType(), ProdKeyType.SPU_NAME.value())) {
                SpuSimpleBO spuSimpleBO = new SpuSimpleBO();
                spuSimpleBO.setLang(I18nMessage.getLang());
                // 商品名称
                spuSimpleBO.setSpuName(stockBillLogDTO.getProdKey());
                List<SpuSimpleBO> spuList = spuService.listSimple(spuSimpleBO);
                List<Long> spuIds = spuList.stream().map(SpuSimpleBO::getSpuId).collect(Collectors.toList());
                criteria.and("spuId").in(spuIds);
            } else if (Objects.equals(stockBillLogDTO.getProdKeyType(), ProdKeyType.PARTY_CODE.value())) {
                logger.info("通过商品编码查询库存流水，商品编码：{}", stockBillLogDTO.getProdKey());
                // 商品编码
                List<SkuVO> skuVoList = skuMapper.listSkuByPartyCodeAndShopId(stockBillLogDTO.getProdKey(), stockBillLogDTO.getShopId());
                // skuId
                List<Long> skuIdList = skuVoList.stream().map(SkuVO::getSkuId).collect(Collectors.toList());
                // 供应商skuId
                List<Long> supplierSkuIdList = skuVoList.stream().map(SkuVO::getSupplierSkuId).collect(Collectors.toList());
                skuIdList.addAll(supplierSkuIdList);
                criteria.and("skuId").in(skuIdList);
            }
        }
        if (CollUtil.isNotEmpty(stockBillLogDTO.getStockBillLogItemIds())) {
            criteria.and("_id").in(stockBillLogDTO.getStockBillLogItemIds());
        }
        return criteria;
    }
}
